#include "sd_card.h"

//注意：CMakeLists.tx中COMPONENT_REQUIRES fatfs  
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
#include "driver/spi_master.h"
#include "driver/sdmmc_host.h"
#include "sdmmc_cmd.h"
#include <cmd_esp32.h>



#define SOC_SDMMC_USE_GPIO_MATRIX 1 //是否使用SDIO

//SPI-SD IO定义
/* SDIO */
#define ESP_SD_PIN_CLK      10
#define ESP_SD_PIN_CMD      9
#define ESP_SD_PIN_D0       12

// #define ESP_SD_PIN_D1       9
// #define ESP_SD_PIN_D2       13
// #define ESP_SD_PIN_D3       14
/* SPI */
#define PIN_NUM_MISO        ESP_SD_PIN_D0
#define PIN_NUM_MOSI        ESP_SD_PIN_CMD
#define PIN_NUM_CLK         ESP_SD_PIN_CLK
#define PIN_NUM_CS          ESP_SD_PIN_D3
#define SPI_DMA_CHAN        SPI_DMA_CH_AUTO
#define TAG "SD_card"


/***************************
本文件使用的全局变量声明
***************************/
// #define g_SD_state globalvariable.SD_state  //SD卡的初始化状态

/***************************
本文件使用的局部变量声明
***************************/
static const char mount_point[] = SD_MOUNT_POINT;
//spisd部分
static sdmmc_card_t card_struct;   //创建结构体变量
static sdmmc_card_t *card=&card_struct;  //创建全局变量指针



#if SOC_SDMMC_USE_GPIO_MATRIX
static esp_err_t init_sd_spi_sdio(void)
{
    esp_err_t ret;

    // Options for mounting the filesystem.
    // If format_if_mount_failed is set to true, SD card will be partitioned and
    // formatted in case when mounting fails.
    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
        .format_if_mount_failed = true,
#else
        .format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };
    // sdmmc_card_t *card;
    // const char mount_point[] = SD_MOUNT_POINT;
    // ESP_LOGI(TAG, "Initializing SD card");

    // Use settings defined above to initialize SD card and mount FAT filesystem.
    // Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
    // Please check its source code and implement error recovery when developing
    // production applications.

    ESP_LOGI(TAG, "Using SDMMC peripheral");
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();
    host.max_freq_khz = SDMMC_FREQ_DEFAULT;

    // This initializes the slot without card detect (CD) and write protect (WP) signals.
    // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); //这里不使用默认设置了

    // To use 1-line SD mode, change this to 1:
    slot_config.width = 1;


    // On chips where the GPIOs used for SD card can be configured, set them in
    // the slot_config structure:
#ifdef SOC_SDMMC_USE_GPIO_MATRIX
    slot_config.clk = ESP_SD_PIN_CLK;
    slot_config.cmd = ESP_SD_PIN_CMD;
    slot_config.d0 = ESP_SD_PIN_D0;
#ifdef USED_SDMMC_WEITH_4
    slot_config.d1 = ESP_SD_PIN_D1;
    slot_config.d2 = ESP_SD_PIN_D2;
    slot_config.d3 = ESP_SD_PIN_D3;
#endif
#endif

    // Enable internal pullups on enabled pins. The internal pullups
    // are insufficient however, please make sure 10k external pullups are
    // connected on the bus. This is for debug / example purpose only.
    slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;

    ESP_LOGI(TAG, "Mounting filesystem");
    ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);

    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) 
        {
            ESP_LOGE(TAG, "Failed to mount filesystem. "
                        "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
        } else 
        {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                        "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        return -1;
    }
    ESP_LOGI(TAG, "Filesystem mounted");

    // Card has been initialized, print its properties
    sdmmc_card_print_info(stdout, card);

    // Use POSIX and C standard library functions to work with files:

#if 1//结束sd卡测试
    // First create a file.
    const char *file_hello = SD_MOUNT_POINT"/hello.txt";

    ESP_LOGI(TAG, "Opening file %s", file_hello);
    FILE *f = fopen(file_hello, "w");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return ;
    }
    fprintf(f, "Hello %s!\n", card->cid.name);
    fclose(f);
    ESP_LOGI(TAG, "File written");

    const char *file_foo = SD_MOUNT_POINT"/foo.txt";

    // Check if destination file exists before renaming
    struct stat st;
    if (stat(file_foo, &st) == 0) {
        // Delete it if it exists
        unlink(file_foo);
    }

    // Rename original file
    ESP_LOGI(TAG, "Renaming file %s to %s", file_hello, file_foo);
    if (rename(file_hello, file_foo) != 0) {
        ESP_LOGE(TAG, "Rename failed");
        return;
    }

    // Open renamed file for reading
    ESP_LOGI(TAG, "Reading file %s", file_foo);
    f = fopen(file_foo, "r");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for reading");
        return;
    }

    // Read a line from file
    char line[64];
    fgets(line, sizeof(line), f);
    fclose(f);

    // Strip newline
    char *pos = strchr(line, '\n');
    if (pos) {
        *pos = '\0';
    }
    ESP_LOGI(TAG, "Read from file: '%s'", line);
#endif //结束sd卡测试
    // All done, unmount partition and disable SDMMC peripheral
    // esp_vfs_fat_sdcard_unmount(mount_point, card);
    // ESP_LOGI(TAG, "Card unmounted");

    return ret;
}
#else

/**
 * 初始化SPI_SD
 */
static esp_err_t init_sd_spi_sdio(void)
{

    //初始化host
    sdmmc_host_t host = SDSPI_HOST_DEFAULT();
    /*
        重新设置sd速度  SDMMC_FREQ_HIGHSPEED 40000 SDMMC_FREQ_DEFAULT 20000 
        spi驱动时 这里使用高速卡可以设置高速，初始化可以通过 但是还是以20m速度运行 不好的内存卡只能以20m配置运行
        以后尝试使用sdio再做测试
        当前测试最好的卡
        Name: SD32G
        Type: SDHC/SDXC
        Speed: 20 MHz
        Size: 30436MB
        读速度723.530090 KB/s
        写速度452.655304 KB/s - 1508.742432 KB/s
    */
    host.max_freq_khz = SDMMC_FREQ_DEFAULT;
    host.slot = SPI3_HOST;//设置spi总线
    ESP_LOGI( TAG, "host.max_freq_khz = %d", host.max_freq_khz );

    esp_err_t ret;
    /**
     * @Step1:初始化mount_config
     * 
     */

    // Options for mounting the filesystem.
    // If format_if_mount_failed is set to true, SD card will be partitioned and
    // formatted in case when mounting fails.
    /*format_if_mount_failed如果设置为true,则如果mount失败，会自动重新初始化sd卡*/
    // esp_vfs_fat_sdspi_mount_config_t 
    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
        .format_if_mount_failed = true,
#else
        .format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };

    /**
     * @Step2:初始化spi
     * 
     */
    ESP_LOGI(TAG, "Initializing SD card");

    // Use settings defined above to initialize SD card and mount FAT filesystem.
    // Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
    // Please check its source code and implement error recovery when developing
    // production applications.
    ESP_LOGI(TAG, "Using SPI peripheral");

    spi_bus_config_t bus_cfg = {
        .miso_io_num=PIN_NUM_MISO,  //MISO信号线，可复用为QSPI的D0
        .mosi_io_num=PIN_NUM_MOSI,  //MOSI信号线，可复用为QSPI的D1
        .sclk_io_num=PIN_NUM_CLK,   //SCLK信号线
        .quadwp_io_num=-1,          //WP信号线，专用于QSPI的D2
        .quadhd_io_num=-1,          //HD信号线，专用于QSPI的D3
        .max_transfer_sz=4096,         //最大传输数据大小，单位字节，默认为4094
        // .intr_flags,//中断指示位
    };
    ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to initialize bus.");
        return ret;
    }

    /**
     * @Step3:初始化SD
     * 
     */

    // This initializes the slot without card detect (CD) and write protect (WP) signals.
    // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
    sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
    slot_config.gpio_cs = PIN_NUM_CS;
    slot_config.host_id = host.slot;

    ESP_LOGI(TAG, "Mounting filesystem");
    ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
    if (ret != ESP_OK) {
        // g_SD_state = 0;
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount filesystem. "
                    "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
        } else {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                    "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        return ret;
    }
    // g_SD_state = 1;
    ESP_LOGI(TAG, "Filesystem mounted");

    // Card has been initialized, print its properties
    sdmmc_card_print_info(stdout, card);
    ret=ESP_OK;
    return ret;
}
#endif

static int sdcard_speed_test_cmd(int argc, char **argv);
void init_Sdcard(void)
{
    //初始化sd卡
    init_sd_spi_sdio(); 
    CONSOLE_REGISTER( "sdcard_speed_test","测试sd卡的读写速度",NULL, sdcard_speed_test_cmd);
}

#define TEST_FILE_SIZE (4 * 1024 * 1024)

static void testWriteFile(const char *path, uint8_t *buf, int len, uint8_t mode)
{
    unsigned long start_time = esp_timer_get_time()/1000;
    ESP_LOGI(TAG, "Test write %s", path);
    if( mode == 1 )
    {
        FILE *fd = fopen(path, "wb");
        if (!fd) 
        {
            ESP_LOGE( TAG, "Failed to open file for writing" );
            return;
        }
        int loop = TEST_FILE_SIZE / len;
        while (loop--)
        {
            if (fwrite(buf, 1, len, fd) <= 0) 
            {
                ESP_LOGE( TAG, "Write failed" );
                return;
            }
        }
        fclose( fd );
    }
    else
    {
        FIL *fd;
        size_t write_len;

        if (ft_open(fd, path, FA_WRITE | FA_OPEN_ALWAYS) != 0) 
        {
            ESP_LOGE( TAG, "Failed to open file for write" );
            return;
        }
        int loop = TEST_FILE_SIZE / len;
        while (loop--)
        {
            if (f_write(fd, buf, len, &write_len) != 0) 
            {
                ESP_LOGE( TAG, "write_len failed" );
                ft_close(fd);
                return;
            }
        }
        ft_close(fd);
    }
    unsigned long time_used = esp_timer_get_time()/1000 - start_time;
    cmd_printf( "Write file used: %ld ms, %f KB/s\r\n", time_used, (float)TEST_FILE_SIZE / time_used);
}

static void testReadFile(const char *path, uint8_t *buf, int len, uint8_t mode)
{

    unsigned long start_time = esp_timer_get_time()/1000;
    ESP_LOGI( TAG, "Test read %s", path );
    if ( mode == 1)
    {
        FILE *fd = fopen(path, "rb");
        if (!fd) 
        {
            ESP_LOGE( TAG, "Failed to open file for reading" );
            return;
        }
        int loop = TEST_FILE_SIZE / len;
        while (loop--)
        {
            if (fread(buf, 1, len, fd) <= 0) 
            {
                ESP_LOGE( TAG, "Read failed" );
                return;
            }
        }
        fclose( fd );
    }
    else
    {
        FIL *fd;
        size_t read_len;

        if (ft_open(fd, path, FA_READ) != 0) 
        {
            ESP_LOGE( TAG, "Failed to open file for read" );
            return;
        }
        int loop = TEST_FILE_SIZE / len;
        while (loop--)
        {
            if (f_read(fd, buf, len, &read_len) != 0) 
            {
                ESP_LOGE( TAG, "Read failed" );
                ft_close(fd);
                return;
            }
        }
        ft_close(fd);
    }
    unsigned long time_used = esp_timer_get_time()/1000 - start_time;
    cmd_printf( "Read file used: %ld ms, %f KB/s\r\n", time_used, (float)TEST_FILE_SIZE / time_used );
    
}

static int sdcard_speed_test_cmd(int argc, char **argv)//SD卡测试代码
{
    /* malloc will not reset all bytes to zero, so it is a random data */
    // uint8_t *buf = (uint8_t*)malloc(64 * 1024);

    uint8_t *buf = (uint8_t*)malloc_ram(64 * 1024);
    printf( "sizeof(FIL)->%d\r\n",sizeof(FIL) );
    ESP_LOGW( TAG, "FATFS test" );
    testWriteFile(FT_MOUNT_POINT"/test_1k.bin", buf, 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_1k.bin", buf, 1024, 0);
    testWriteFile(FT_MOUNT_POINT"/test_2k.bin", buf, 2 * 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_2k.bin", buf, 2 * 1024, 0);
    testWriteFile(FT_MOUNT_POINT"/test_4k.bin", buf, 4 * 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_4k.bin", buf, 4 * 1024, 0);
    testWriteFile(FT_MOUNT_POINT"/test_8k.bin", buf, 8 * 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_8k.bin", buf, 8 * 1024, 0);
    testWriteFile(FT_MOUNT_POINT"/test_16k.bin", buf, 16 * 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_16k.bin", buf, 16 * 1024, 0);
    testWriteFile(FT_MOUNT_POINT"/test_32k.bin", buf, 32 * 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_32k.bin", buf, 32 * 1024, 0);
    testWriteFile(FT_MOUNT_POINT"/test_64k.bin", buf, 64 * 1024, 0);
    testReadFile(FT_MOUNT_POINT"/test_64k.bin", buf, 64 * 1024, 0);

    ESP_LOGW( TAG, "FILE test" );
    testWriteFile(SD_MOUNT_POINT"/test_1k.bin", buf, 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_1k.bin", buf, 1024, 1);
    testWriteFile(SD_MOUNT_POINT"/test_2k.bin", buf, 2 * 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_2k.bin", buf, 2 * 1024, 1);
    testWriteFile(SD_MOUNT_POINT"/test_4k.bin", buf, 4 * 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_4k.bin", buf, 4 * 1024, 1);
    testWriteFile(SD_MOUNT_POINT"/test_8k.bin", buf, 8 * 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_8k.bin", buf, 8 * 1024, 1);
    testWriteFile(SD_MOUNT_POINT"/test_16k.bin", buf, 16 * 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_16k.bin", buf, 16 * 1024, 1);
    testWriteFile(SD_MOUNT_POINT"/test_32k.bin", buf, 32 * 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_32k.bin", buf, 32 * 1024, 1);
    testWriteFile(SD_MOUNT_POINT"/test_64k.bin", buf, 64 * 1024, 1);
    testReadFile(SD_MOUNT_POINT"/test_64k.bin", buf, 64 * 1024, 1);


    free(buf);

    return 0;
}